#include "mul.h"
#include "objectloader.h"
#include "shaderprogram.h"
#include "texture.h"
#include "framebuffer.h"
#include "framebufferhandler.h"
#include "vertexarrayobject.h"
#include "bufferobject.h"
#include "textureplane.h"

#include <glm/glm.hpp>
#include <glm/gtc/matrix_projection.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtx/transform2.hpp>
#include <glm/gtc/type_ptr.hpp>


#include <lib3ds.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <assert.h>
#include <vector>

#ifndef MIN
#define MIN(a,b) ((a)<(b)?(a):(b))
#define MAX(a,b) ((a)>(b)?(a):(b))
#endif


using namespace std;


////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// Data
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

vector<unsigned int> shaders;

bool animation = false;
bool emptyShaders = false;

int width = 800, height = 600;// Window size
float rx = 0.0f, ry = 0.0f, px = 0.0f, py = 0.0f, pz = 0.0f;
float posX = 0.0f, posY = 0.0f;

unsigned mode = 0;
unsigned cameraIndex = 0;
unsigned lightIndex = 0;

bool phong = true;
float shininess = 4;
int radius = 15;
int T = 0;

bool swirlUsed = false;
int OPvariable = 8;

const unsigned int dithering    = 0b0000000000000001;
const unsigned int sobel        = 0b0000000000000010;
const unsigned int wave         = 0b0000000000000100;
const unsigned int pixelization = 0b0000000000001000;
const unsigned int swirl        = 0b0000000000010000;
const unsigned int night        = 0b0000000000100000;
const unsigned int gauss        = 0b0000000001000000;
const unsigned int ascii	= 0b0000000010000000;

SDL_TimerID timer;
SDL_TimerID chrrrTimer;

int chrrr = 0;
bool chrrrAnimation = false;

ObjectLoader *loader;
ObjectLoader *planeLoader;
ShaderProgram *objectProgram;
ShaderProgram *postProcessGauss;
ShaderProgram *postProcessGauss2;
ShaderProgram *postProcessDithering;
ShaderProgram *postProcessSobel;
ShaderProgram *postProcessWave;
ShaderProgram *postProcessASCII;
ShaderProgram *postProcessFinal;

ShaderProgram * postPixelization;
ShaderProgram * postSwirl;
ShaderProgram * postNight;

FramebufferHandler *framebufferHandler;

VertexArrayObject *objectVAO;
VertexArrayObject *postProcessVAO;

TexturePlane *texturePlane;

BufferObject *objectVBO;
BufferObject *objectEBO;
BufferObject *planeVBO;
BufferObject *planeEBO;

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// Shaders
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
GLint mvpUniform, mvUniform, mnUniform;

GLint lightPositionUniform, shininessUniform, modeUniform;
GLuint textureUniform;
GLuint textureUniform2;
GLuint textureUniform3;
GLuint textureUniform4;
GLuint textureUniform5;
GLuint textureUniform9;
GLuint finalTextureUniform;
GLuint chrrrUniform;

GLuint textureUniform6; GLuint radiusUniform6;
GLuint textureUniform7; GLuint angleUniform7; GLuint xUniform7; GLuint yUniform7;
GLuint textureUniform8; GLuint timeUniform8;

GLuint tUniform;
GLuint radiusUniform, movementUniform;
GLuint radiusUniform2, movementUniform2;

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// Initialization + event loop
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

SDL_Surface * init(unsigned width, unsigned height, unsigned color, unsigned depth, unsigned stencil)
{
    // Set OpenGL attributes
    if(SDL_GL_SetAttribute(SDL_GL_BUFFER_SIZE, color) < 0) throw SDL_Exception();
    if(SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, depth) < 0) throw SDL_Exception();
    if(SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, stencil) < 0) throw SDL_Exception();
    if(SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1) < 0) throw SDL_Exception();

    // Create window
    SDL_Surface * screen = SDL_SetVideoMode(width, height, color, SDL_OPENGL | SDL_RESIZABLE);
    if(screen == NULL) throw SDL_Exception();

    glewInit();
    
    // Call init code
    onInit();

    onWindowResized(width, height);

    return screen;
}

// Simple main loop
void mainLoop()
{
    // Window is not minimized
    bool active = true;

    for(;;)// Infinite loop
    {
        SDL_Event event;
        
        // Wait for event
        if(SDL_WaitEvent(&event) == 0) throw SDL_Exception();

        // Screen needs redraw
        bool redraw = false;

        // Handle all waiting events
        do
        {
            // Call proper event handlers
            switch(event.type)
            {
                case SDL_ACTIVEEVENT :// Stop redraw when minimized
                    if(event.active.state == SDL_APPACTIVE)
                        active = event.active.gain;
                    break;
                case SDL_KEYDOWN :
                    onKeyDown(event.key.keysym.sym, event.key.keysym.mod);
                    break;
                case SDL_KEYUP :
                    onKeyUp(event.key.keysym.sym, event.key.keysym.mod);
                    break;
                case SDL_MOUSEMOTION :
                    onMouseMove(event.motion.x, event.motion.y, event.motion.xrel, event.motion.yrel, event.motion.state);
                    break;
                case SDL_MOUSEBUTTONDOWN :
                    onMouseDown(event.button.button, event.button.x, event.button.y);
                    break;
                case SDL_MOUSEBUTTONUP :
                    onMouseUp(event.button.button, event.button.x, event.button.y);
                    break;
                case SDL_QUIT :
                    return;// End main loop
                case SDL_VIDEORESIZE :
                    onWindowResized(event.resize.w, event.resize.h);
                    break;
                case SDL_VIDEOEXPOSE :
                    redraw = true;
                    break;
                default :// Do nothing
                    break;
            }
        } while(SDL_PollEvent(&event) == 1);

        // Optionally redraw window
        if(active && redraw) onWindowRedraw();
    }
}


////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// Event handlers
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/**
 * Check OpenGL error
 */
void checkError(const char *title) {
    GLenum err = glGetError();
    if(err){
        printf("%s: ", title);
        switch(err){
            case GL_INVALID_ENUM:      
                printf("Error: invalid enum.");
                break;
            case GL_INVALID_VALUE:
                printf("Error: invalid value.");
                break;
            case GL_INVALID_OPERATION:
                printf("Error: invalid operation.");
                break;
            case GL_INVALID_FRAMEBUFFER_OPERATION:
                printf("Error: invalid framebuffer operation.");
                break;
            case GL_OUT_OF_MEMORY:
                printf("Error: out of memory.");
                break;
            
        }
        printf("\n");
        fflush(stdout);
    }
}

/**
 * Initialize object and it's shaders, EBO and VBO
 */
void initObject () {
    
    // create shader for object
    objectProgram = new ShaderProgram("shaders/object.vs", "shaders/object.fs");
    // get uniform locations
    mvpUniform           = objectProgram->getUniformLocation("mvp");
    mvUniform            = objectProgram->getUniformLocation("mv");
    mnUniform            = objectProgram->getUniformLocation("mn");
    lightPositionUniform = objectProgram->getUniformLocation("lightPosition");
    shininessUniform     = objectProgram->getUniformLocation("shininess");
    modeUniform          = objectProgram->getUniformLocation("mode");
    
    // get count of vertices and indices of object
    unsigned int vertexCount = loader->getVertexCount();
    unsigned int indexCount = loader->getIndexCount();
    
    // get indices and vertices
    ObjectVertex *vertices = loader->getVertices();
    VertexIndices *indices = loader->getIndices();

    // create new VBO
    objectVBO = new BufferObject(GL_ARRAY_BUFFER);
    // set it's data
    objectVBO->setData(sizeof(ObjectVertex)*vertexCount, vertices);
    
    // create new EBO
    objectEBO = new BufferObject(GL_ELEMENT_ARRAY_BUFFER);
    // set it's data
    objectEBO->setData(sizeof(VertexIndices)*indexCount, indices);
    
    // create VAO for object, automatically binds itself
    objectVAO = new VertexArrayObject();
    
    // 3 vertex indices per one index (triagle)    
    objectVAO->setDrawElements(GL_TRIANGLES, 3*indexCount, GL_UNSIGNED_INT, NULL);
    objectVAO->setClearMask(GL_DEPTH_BUFFER_BIT| GL_COLOR_BUFFER_BIT);
    // bind VBO and EBO buffers to VAO
    objectVBO->bind();
    objectEBO->bind();
    
    // add attributes to shader, VAO must binded
    objectProgram->addAttribute("position", 3, GL_FLOAT, GL_FALSE, sizeof(ObjectVertex), (void*)offsetof(ObjectVertex, position));
    objectProgram->addAttribute("normal", 3, GL_FLOAT, GL_FALSE, sizeof(ObjectVertex), (void*)offsetof(ObjectVertex, normal));
    objectProgram->addAttribute("color", 4, GL_FLOAT, GL_FALSE, sizeof(ObjectVertex), (void*)(offsetof(ObjectVertex, color)+offsetof(VertexColor, diffuse)));    
}

/**
 * Initialize texture plane which is used for postprocessing
 * We will render object into texture which uses this plane
 */
void initTexturePlane () {
        
    // create new texture plane, use by post processing
    // we will render object into texture, with is this plane
    texturePlane = new TexturePlane();
    
    // get vertices and indices of plane
    GLfloat *texturePlaneVertices = texturePlane->getVertices();
    GLuint *texturePlaneIndices = texturePlane->getIndices();
    
    // get count of indices and vertices
    GLuint texturePlaneVertexCount = texturePlane->getVertexCount();
    GLuint texturePlaneIndexCount = texturePlane->getIndexCount();

    // new VBO for plane
    planeVBO = new BufferObject(GL_ARRAY_BUFFER);
    // set it's data
    planeVBO->setData(sizeof(GLfloat)*texturePlaneVertexCount, (GLvoid *) texturePlaneVertices);
    
    // new EBO for plane
    planeEBO = new BufferObject(GL_ELEMENT_ARRAY_BUFFER);
    // set it's data
    planeEBO->setData(sizeof(GLuint)*texturePlaneIndexCount, (GLvoid *) texturePlaneIndices);
    
}

/**
 * Initialize postprocess shaders, VBOs, EBOs used while
 * postprocessing image
 * 
 */
void initPostProcesses () {
    
    // create new vertex array object, automatically binds itself
    // !!! add attribute to shader must call before VAO is binded !!!
    postProcessVAO = new VertexArrayObject();
    postProcessVAO->setClearMask(GL_DEPTH_BUFFER_BIT| GL_COLOR_BUFFER_BIT);
    postProcessVAO->setDrawElements(GL_TRIANGLES, texturePlane->getIndexCount(), GL_UNSIGNED_INT, NULL);
    
    // bind planes VBO and EBO to VAO
    planeVBO->bind();
    planeEBO->bind();
    
    
    // create shader program and bind attributes and uniforms to VAO
    postProcessGauss = new ShaderProgram("shaders/postprocess.vs", "shaders/gauss_blur.fs");
    postProcessGauss->addAttribute("position", 4, GL_FLOAT, GL_FALSE, 0, NULL);
    
    textureUniform = postProcessGauss->getUniformLocation("tex");
    radiusUniform = postProcessGauss->getUniformLocation("radius");
    movementUniform = postProcessGauss->getUniformLocation("movement");

    
    // create shader program and bind attributes and uniforms to VAO
    postProcessGauss2 = new ShaderProgram("shaders/postprocess.vs", "shaders/gauss_blur.fs");
    postProcessGauss2->addAttribute("position", 4, GL_FLOAT, GL_FALSE, 0, NULL);    
    
    textureUniform2 = postProcessGauss2->getUniformLocation("tex");
    radiusUniform2 = postProcessGauss2->getUniformLocation("radius");
    movementUniform2 = postProcessGauss2->getUniformLocation("movement");    
    
    postProcessDithering = new ShaderProgram("shaders/postprocess.vs", "shaders/dithering.fs");
    postProcessDithering->addAttribute("position", 4, GL_FLOAT, GL_FALSE, 0, NULL);    

    textureUniform3 = postProcessDithering->getUniformLocation("tex");

    postProcessSobel = new ShaderProgram("shaders/postprocess.vs", "shaders/sobel.fs");
    postProcessSobel->addAttribute("position", 4, GL_FLOAT, GL_FALSE, 0, NULL);    

    textureUniform4 = postProcessSobel->getUniformLocation("tex");
    
    postProcessWave = new ShaderProgram("shaders/postprocess.vs", "shaders/sin.fs");
    postProcessWave->addAttribute("position", 4, GL_FLOAT, GL_FALSE, 0, NULL);    

    textureUniform5 = postProcessWave->getUniformLocation("tex");

	postProcessASCII = new ShaderProgram("shaders/postprocess.vs", "shaders/ascii.fs");
	postProcessASCII->addAttribute("position", 4, GL_FLOAT, GL_FALSE, 0, NULL);
	textureUniform9 = postProcessASCII->getUniformLocation("tex");

	postPixelization = new ShaderProgram("shaders/postprocess.vs", "shaders/pixel.fs");
	postPixelization->addAttribute("position", 4, GL_FLOAT, GL_FALSE, 0, NULL);
	textureUniform6 = postPixelization->getUniformLocation("tex");
	radiusUniform6 = postPixelization->getUniformLocation("radius");

	postSwirl = new ShaderProgram("shaders/postprocess.vs", "shaders/swirl.fs");
	postSwirl->addAttribute("position", 4, GL_FLOAT, GL_FALSE, 0, NULL);
	textureUniform7 = postSwirl->getUniformLocation("tex");
	angleUniform7 = postSwirl->getUniformLocation("angle");
	xUniform7 = postSwirl->getUniformLocation("x");
	yUniform7 = postSwirl->getUniformLocation("y");

	postNight = new ShaderProgram("shaders/postprocess.vs", "shaders/night.fs");
	postNight->addAttribute("position", 4, GL_FLOAT, GL_FALSE, 0, NULL);
	textureUniform8 = postNight->getUniformLocation("tex");
	timeUniform8 = postNight->getUniformLocation("time");
	chrrrUniform = postNight->getUniformLocation("chrrr");
    
    postProcessFinal = new ShaderProgram("shaders/postprocess.vs", "shaders/final.fs");
    postProcessFinal->addAttribute("position", 4, GL_FLOAT, GL_FALSE, 0, NULL);
    finalTextureUniform = postProcessFinal->getUniformLocation("tex");    
}

/**
 * Initialize framebuffers
 */
void initFramebuffers () {

    // create new framebuffer handler
    framebufferHandler = new FramebufferHandler();
    
    Framebuffer *framebuffer;

    // create textures for framebuffer handler
    Texture *texture = new Texture(GL_RGBA, GL_RGBA);
    Texture *depthTexture = new Texture(GL_DEPTH_COMPONENT, GL_DEPTH_COMPONENT);
    
    // first framebuffer must have depth attachment
    // because we will render object first into this framebuffer
    framebuffer = new Framebuffer(GL_FRAMEBUFFER);
    framebuffer->addTexture2D(GL_COLOR_ATTACHMENT0, texture, GL_TEXTURE0);
    framebuffer->addTexture2D(GL_DEPTH_ATTACHMENT, depthTexture);
    // draw it's buffers
    framebuffer->drawBuffers();
    
    // add framebuffer to the handler
    framebufferHandler->addBuffer(framebuffer);

    
    // create texture for the second framebuffer
    texture = new Texture(GL_RGBA, GL_RGBA);
    
    // this framebuffer contains only color attachment
    framebuffer = new Framebuffer(GL_FRAMEBUFFER);
    framebuffer->addTexture2D(GL_COLOR_ATTACHMENT0, texture, GL_TEXTURE0);
    framebuffer->drawBuffers();
    
    // add framebuffer to the handler
    framebufferHandler->addBuffer(framebuffer);
    
    // create the last framebuffer
    // this is the default framebuffer
    // this must be called last
    // this will contain the final image
    framebuffer = new Framebuffer(GL_FRAMEBUFFER, 0);
    
    glClearColor(0.5, 0.5, 0.5, 1.0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);    
    
    // add framebufer to handler, but now as default framebuffer
    framebufferHandler->addBuffer(framebuffer, true);
}

void onInit () {
        
    // init object, VAO and it's shader
    initObject();
    
    // init texture plane for texturing
    initTexturePlane();
    
    //init post process shaders and VAO
    initPostProcesses();

    // init framebuffers and textures
    initFramebuffers();
    
}

/**
 * Render object, calculate MVP, set lights and uniform variables
 */
void renderObject () {
    // get first framebuffer, which has depth attachment
    framebufferHandler->bindFirst();
    
    // bind vao
    objectVAO->bind();
    
    glEnable(GL_DEPTH_TEST);
    glCullFace(GL_BACK);
    glDisable(GL_CULL_FACE); 

    // Calculate ModelViewProjection matrix
    glm::mat4 lookAt;
    glm::mat4 projection;
    
    if (loader->getCameraCount() > 0) {
        Camera *camera = loader->getCamera(cameraIndex);

        projection = 
            glm::perspective(camera->fovy, (float)width/(float)height, camera->near, camera->far);

    
        lookAt = glm::lookAt(
            glm::vec3(camera->eye),
            glm::vec3(camera->center),
            glm::vec3(camera->up)
        );
    
    }
  
    glm::mat4 mv = 
        glm::rotate(
            glm::rotate(
                glm::translate(
                    glm::mat4(1.0f),
                    glm::vec3(0, 0, pz)
                ),
                ry, glm::vec3(1, 0, 0)
            ),
            rx, glm::vec3(0, 1, 0)
        );

    glm::mat4 mvp = projection*lookAt*mv;

    glm::mat3 mn = glm::mat3(
        glm::rotate(
            glm::rotate(
                glm::mat4(1.0f),
                ry, glm::vec3(1, 0, 0)
                ),
            rx, glm::vec3(0, 1, 0)
            )
        );

    // use shader
    objectProgram->use();

    // Set matrices
    glUniformMatrix4fv(mvpUniform, 1, GL_FALSE, glm::value_ptr(mvp)); 
    glUniformMatrix4fv(mvUniform, 1, GL_FALSE, glm::value_ptr(mv)); 
    glUniformMatrix3fv(mnUniform, 1, GL_FALSE, glm::value_ptr(mn)); 

    // get light of object
    Light light = loader->getLight(lightIndex);
    
    // get uniform variables
    glUniform3f(lightPositionUniform, light.position[0], light.position[1], light.position[2]);
    glUniform1f(shininessUniform, shininess);
    glUniform1f(modeUniform, phong ? (mode + 0.1f) : (-0.1f - mode));

    // draw object
    objectVAO->drawElements();
}

/**
 * Render planes and textures, call postprocessing programs
 */
void renderPostProcess () {
    
    if (emptyShaders) {
        emptyShaders = false;
        animation = false;
	chrrrAnimation = false;
        swirlUsed = false;
        SDL_RemoveTimer(timer);
	SDL_RemoveTimer(chrrrTimer);
        shaders.clear();
    }
    
    for (unsigned int i=0; i<shaders.size(); i++) {
        if (shaders.at(i) == gauss) {
            // bind next framebufer and textures of previous framebuffer
            // to work with them
            framebufferHandler->bindNext();
            
            // bind VAO
            postProcessVAO->bind();
            
            // use shader program
            postProcessGauss->use();
            
            // set uniforms
            glUniform1i(textureUniform, 0);  
            glUniform1i(radiusUniform, radius);
            glUniform2f(movementUniform, 0.0f, 1.0f);
            
            // draw plane
            postProcessVAO->drawElements();

            ///////////////////////////////////////////////////////////////////////////
            
            // bind default framebuffer, which must contain final rendered image
            framebufferHandler->bind();
            
            // bind VAO
            postProcessVAO->bind();
            
            // use shader
            postProcessGauss2->use();
            
            // set uniform variables
            glUniform1i(textureUniform2, 0);  
            glUniform1i(radiusUniform2, radius);
            glUniform2f(movementUniform2, 1.0f, 0.0f);
            
            // draw plane
            postProcessVAO->drawElements();            
        } else if (shaders.at(i) == dithering) {
            // Dithering
            framebufferHandler->bindNext();
            postProcessVAO->bind();
            postProcessDithering->use();
            glUniform1i(textureUniform3, 0);
            postProcessVAO->drawElements();
        } else if (shaders.at(i) == sobel) {
            // Sobel filter    
            framebufferHandler->bindNext();
            postProcessVAO->bind();
            postProcessSobel->use();
            glUniform1i(textureUniform4, 0);
            postProcessVAO->drawElements();
        } else if (shaders.at(i) == wave) {
            // Wave filter    
            framebufferHandler->bindNext();
            postProcessVAO->bind();
            postProcessWave->use();
            glUniform1i(textureUniform5, 0);
            glUniform1i(tUniform, T);
            postProcessVAO->drawElements();
        } else if (shaders.at(i) == pixelization) {
            // Pixelization
            framebufferHandler->bindNext();
            postProcessVAO->bind();
            postPixelization->use();
            glUniform1i(textureUniform6, 0);
            glUniform1i(radiusUniform6, OPvariable);
            postProcessVAO->drawElements();
        } else if (shaders.at(i) == swirl) {
            // Swirl
            framebufferHandler->bindNext();
            postProcessVAO->bind();
            postSwirl->use();
            glUniform1i(textureUniform7, 0);
            glUniform1i(angleUniform7, OPvariable);
            glUniform1f(xUniform7, posX);
            glUniform1f(yUniform7, posY);
            postProcessVAO->drawElements();
        } else if (shaders.at(i) == night) {
            // Night vision
            framebufferHandler->bindNext();
            postProcessVAO->bind();
            postNight->use();
            glUniform1i(textureUniform8, 0);
	        glUniform1i(chrrrUniform, chrrr);
            glUniform1f(timeUniform8, (float)T);
            postProcessVAO->drawElements();
        } else if(shaders.at(i) == ascii){
            framebufferHandler->bindNext();
            postProcessVAO->bind();
            postProcessASCII->use();
            glUniform1i(textureUniform9, 0);
            postProcessVAO->drawElements();
	}
	
    }
    
    ///////////////////////////////////////////////////////////////////////////
    
    // bind default framebuffer, which must contain final rendered image
    framebufferHandler->bind(true);
    
    // bind VAO
    postProcessVAO->bind();
    
    // use shader
    postProcessFinal->use();
    
    // set uniform variables
    glUniform1i(finalTextureUniform, 0);  
    
    // draw plane
    postProcessVAO->drawElements();
}

Uint32 sinWave(Uint32 interval, void* param){
	T++; 
	redraw();
	return interval;
}

Uint32 chrrrWave(Uint32 interval, void* param){
	chrrr = 0;
	if (float(rand()) / RAND_MAX > 0.9)
		chrrr = 1;
	redraw();
	return interval;
}

void onWindowRedraw ()
{
    // render object into texture
    renderObject();
    
    // post process the textures
    renderPostProcess();
    
    SDL_GL_SwapBuffers();
}

void onWindowResized(int w, int h)
{
    // Recreate window
    // On window can cause problems, just delete this two lines
    SDL_Surface * screen = SDL_SetVideoMode(width, height, 24, SDL_OPENGL | SDL_RESIZABLE);
    if(screen == NULL) throw SDL_Exception();
    
    glViewport(0, 0, w, h);
    width = w; height = h;

    // resize framebuffers' textures
    framebufferHandler->resizeTextures(width, height);
 
}

void onKeyDown(SDLKey key, Uint16 /*mod*/)
{
    switch(key) {
        case SDLK_ESCAPE : quit(); break;
        case SDLK_g : phong = !phong; redraw(); break;
        case SDLK_j : mode = 0; redraw(); break;
        case SDLK_h : mode = 1; redraw(); break;
        case SDLK_c : cameraIndex = (cameraIndex+1) % loader->getCameraCount(); redraw(); break;
        case SDLK_l : lightIndex = (lightIndex+1) % loader->getLightCount(); redraw(); break;
        case SDLK_r : radius++; redraw(); break;
        case SDLK_e : radius--; redraw(); break;
        case SDLK_o : if (OPvariable > 0) --OPvariable; redraw(); break;
        case SDLK_p : ++OPvariable; redraw(); break;        
             
        case SDLK_F1 : shaders.push_back(dithering); redraw(); break;
        case SDLK_F2 : shaders.push_back(sobel); redraw(); break;
        case SDLK_F3 : 
                        if (!animation) {
                            timer = SDL_AddTimer(50, sinWave, NULL);
                            T = 0;
                            animation = true;
                        }
                        shaders.push_back(wave);
                        redraw(); break;

        case SDLK_F4 : shaders.push_back(pixelization); redraw(); break;
        case SDLK_F5 : swirlUsed = true; shaders.push_back(swirl); redraw(); break;
        case SDLK_F6 :
                        if (!animation) {
                            timer = SDL_AddTimer(50, sinWave, NULL);
                            T = 0;
                            animation = true;
                        }
			if(!chrrrAnimation){
				chrrrTimer = SDL_AddTimer(200, chrrrWave, NULL);
				chrrrAnimation = true;
			}
                        shaders.push_back(night);
                        redraw(); break;
        case SDLK_F7 : shaders.push_back(gauss); redraw(); break;
	case SDLK_F8 : shaders.push_back(ascii); redraw(); break;
        case SDLK_F12 : emptyShaders = true; redraw(); break;   
//        case SDLK_3 : mode = 2; redraw(); break;
//        case SDLK_4 : mode = 3; redraw(); break;
        default : break; //nothing-doing defaut to shut up warning
    }
}

void onKeyUp(SDLKey /*key*/, Uint16 /*mod*/)
{
}

void onMouseMove(unsigned x, unsigned y, int xrel, int yrel, Uint8 buttons)
{
    posX = x; posY = height - y;
    if (swirlUsed) {
        redraw();
    }

    if(buttons & SDL_BUTTON_LMASK)
    {
        rx += xrel;
        ry += yrel;
        redraw();
    }
    if(buttons & SDL_BUTTON_RMASK)
    {
        pz += yrel;
        redraw();
    }
}

void onMouseDown(Uint8 button, unsigned /*x*/, unsigned /*y*/)
{
    switch(button) {
        case SDL_BUTTON_WHEELUP : shininess *= 2; redraw(); break;
        case SDL_BUTTON_WHEELDOWN : shininess /= 2; redraw(); break;
        default : return;
    }
}

void onMouseUp(Uint8 /*button*/, unsigned /*x*/, unsigned /*y*/)
{
}

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
// Main
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

int main(int /*argc*/, char ** argv)
{
    srand(time(NULL)); 
    loader = new ObjectLoader(argv[1]);
    
    
    try {
        // Init SDL - only video subsystem will be used
        if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) < 0) throw SDL_Exception();

        // Shutdown SDL when program ends
        atexit(SDL_Quit);

        init(800, 600, 24, 24, 0);

        mainLoop();

    } catch(exception & ex) {
        cout << "ERROR : " << ex.what() << endl;
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

